
package de.justphil.tcg.tcgserver.game.mechanics.monster;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import de.justphil.tcg.tcgserver.commons.exceptions.TCGException;
import de.justphil.tcg.tcgserver.game.mechanics.event.Event;
import de.justphil.tcg.tcgserver.game.mechanics.event.EventObservable;
import de.justphil.tcg.tcgserver.game.mechanics.event.Scheduler;
import de.justphil.tcg.tcgserver.game.mechanics.game.Game;
import de.justphil.tcg.tcgserver.game.mechanics.game.GameDataSerializable;
import de.justphil.tcg.tcgserver.game.mechanics.property.Property;
import de.justphil.tcg.tcgserver.game.mechanics.property.PropertyFactory;
import de.justphil.tcg.tcgserver.game.mechanics.property.PropertyList;
import de.justphil.tcg.tcgserver.game.mechanics.util.tools.IterationHelper;

import org.slf4j.Logger;
import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;

public class Monster implements GameDataSerializable {
	
	private static final Logger	log	= gameLogger(Monster.class);
	private long				id;
	private int					life;
	private int					attack;
	private int					defense;
	private int					speed;
	private boolean				active;
	private boolean				deactivationInProgress;
	private PropertyList		properties;
	private Game				game;
	private int[]				propertyTypes;
	
	public Monster(long id) {
	
		// only needed in order to conform to the DataSerializable
		this.id = id;
	}
	
	public Monster(long id, int life, int attack, int defense, int speed,
			int[] propertyTypes) {
	
		this.life = life;
		this.attack = attack;
		this.speed = speed;
		this.defense = defense;
		this.id = id;
		this.active = false;
		this.deactivationInProgress = false;
		this.propertyTypes = propertyTypes;
		this.properties = new PropertyList();
	}
	
	public void setGame(Game game) {
	
		this.game = game;
		for (int propertyType : propertyTypes) {
			addProperty(PropertyFactory.getProperty(game, propertyType));
		}
	}
	
	public boolean isActive() {
	
		return this.active;
	}
	
	public boolean isDeactivationInProgress() {
	
		return this.deactivationInProgress;
	}
	
	public PropertyList getProperties() {
	
		return this.properties;
	}
	
	public PropertyList getProperties(int type) {
	
		PropertyList selectedProperties = new PropertyList();
		for (Property property : properties) {
			if (property.getType() == type) {
				selectedProperties.add(property);
			}
		}
		return selectedProperties;
	}
	
	public PropertyList getActiveProperties(int type) {
	
		PropertyList selectedProperties = new PropertyList();
		for (Property property : properties) {
			if (property.getType() == type && property.isActive()) {
				selectedProperties.add(property);
			}
		}
		return selectedProperties;
	}
	
	public PropertyList getActiveProperties() {
	
		PropertyList selectedProperties = new PropertyList();
		for (Property property : properties) {
			if (property.isActive()) {
				selectedProperties.add(property);
			}
		}
		return selectedProperties;
	}
	
	public boolean hasProperty(int type) {
	
		return getProperties(type).size() > 0;
	}
	
	public boolean hasActiveProperty(int type) {
	
		return getActiveProperties(type).size() > 0;
	}
	
	public long getId() {
	
		return id;
	}
	
	public int getLife() {
	
		int currentLife = this.life;
		for (Property property : this.properties) {
			if (property.isActive())
				currentLife += property.getLifeModification();
		}
		return currentLife;
	}
	
	public int getAttack() {
	
		int currentAttack = this.attack;
		for (Property property : this.properties) {
			if (property.isActive())
				currentAttack += property.getAttackModification();
		}
		return currentAttack;
	}
	
	public int getSpeed() {
	
		int currentSpeed = this.speed;
		for (Property property : this.properties) {
			if (property.isActive())
				currentSpeed += property.getSpeedModification();
		}
		return currentSpeed;
	}
	
	public int getDefense() {
	
		int currentDefens = this.defense;
		for (Property property : this.properties) {
			if (property.isActive())
				currentDefens += property.getDefenseModification();
		}
		return currentDefens;
	}
	
	public EventObservable getEventObservable() {
	
		return game.getEventObservable();
	}
	
	public Scheduler getEventScheduler() {
	
		return game.getScheduler();
	}
	
	public void addProperty(Property addProperty) {
	
		addProperty.setMonster(this);
		if (isActive()) {
			/* Only activate if the property is stackable or if it is the first of its kind. */
			if (addProperty.isStackable()) {
				addProperty.activate();
			}
			else {
				boolean activate = true;
				for (Property monsterProperty : properties) {
					if (monsterProperty.getType() == addProperty.getType()) {
						activate = false;
					}
				}
				if (activate) {
					addProperty.activate();
				}
				else {
					log.debug("Not activating a Property ({}) because it's unstackable.",
							addProperty);
				}
			}
		}
		this.properties.add(addProperty);
		log.debug("Added Property ({})", addProperty);
	}
	
	public boolean removeProperty(Property removeProperty) {
	
		/* Searching if another Property must be activated instead of the removed Property. */
		Property activateProperty = null;
		if (!removeProperty.isStackable() && removeProperty.isActive()) {
			for (Property monsterProperty : properties) {
				if (monsterProperty != removeProperty
						&& monsterProperty.getType() == removeProperty.getType()) {
					activateProperty = monsterProperty;
					break;
				}
			}
		}
		removeProperty.deactivate();
		removeProperty.setMonster(null);
		if (activateProperty != null) {
			log.debug("Activating Property ({}) on removal of Property ({})", activateProperty,
					removeProperty);
			activateProperty.activate();
		}
		boolean removed = this.properties.remove(removeProperty);
		if (removed) {
			try {
				getEventScheduler().scheduleFirst(
						new Event(Event.EVENT_PROPERTY_DEACTIVATED, this), 0.0);
			}
			catch (TCGException ignored) {
				/* If multiple Properties are removed in a row, one PROPERTY REMOVED Event is
				 * sufficient. */
			}
			log.debug("Removed Property ({}) form Monster ID:{}", removeProperty, this.getId());
		}
		else {
			log.debug("Property ({}) could not be removed form Monster ID:{}", removeProperty,
					this.getId());
		}
		return removed;
	}
	
	public void activate() {
	
		if (!isActive()) {
			log.debug("Activating Monster ({})", this);
			// TODO what if the property had been deactivated through another
			// property?
			// Working around a ConcurrencyException.
			new IterationHelper<Property>(properties) {
				
				@Override
				protected void execute(Property listElement) {
				
					listElement.activate();
				}
			}.safeExecuteForAll();
			active = true;
		}
	}
	
	public void deactivate() {
	
		if (isActive()) {
			log.debug("Dectivating Monster ({})", this);
			deactivationInProgress = true;
			new IterationHelper<Property>(properties) {
				
				@Override
				protected void execute(Property listElement) {
				
					listElement.deactivate();
				}
			}.safeExecuteForAll();
			active = false;
			deactivationInProgress = false;
		}
	}
	
	@Override
	public void writeData(Game game, DataOutput out) throws IOException {
	
		log.info("Writing Monster ({}).", this);
		out.writeInt(life);
		out.writeInt(attack);
		out.writeInt(defense);
		out.writeInt(speed);
		out.writeBoolean(active);
		out.writeBoolean(deactivationInProgress);
		out.writeInt(properties.size());
		for (Property p : properties) {
			game.getDataSerializableManager().writeProperty(p, game, out);
		}
	}
	
	@Override
	public void readData(Game game, DataInput in) throws IOException {
	
		log.info("Reading Monster ({}).", this.id);
		life = in.readInt();
		attack = in.readInt();
		defense = in.readInt();
		speed = in.readInt();
		active = in.readBoolean();
		deactivationInProgress = in.readBoolean();
		int propertySize = in.readInt();
		properties = new PropertyList();
		Property p = null;
		this.game = game;
		for (int i = 0; i < propertySize; i++) {
			p = game.getDataSerializableManager().readProperty(game, in);
			properties.add(p);
			p.setMonster(this);
		}
	}
	
	@Override
	public String toString() {
	
		StringBuffer result = new StringBuffer();
		result.append("ID:");
		result.append(getId());
		result.append("|L:");
		result.append(getLife());
		result.append("|A:");
		result.append(getAttack());
		result.append("|D:");
		result.append(getDefense());
		result.append("|A:");
		result.append(getSpeed());
		result.append("|P:");
		result.append(getProperties());
		result.append("");
		return new String(result);
	}
	
	/**
	 * Two Monsters are equal if their ids are equal.
	 * 
	 */
	@Override
	public boolean equals(Object obj) {
	
		if (!Monster.class.isInstance(obj)) {
			return false;
		}
		Monster monster = (Monster) obj;
		return monster.id == this.id;
	}
	
	/**
	 * return id
	 */
	@Override
	public int hashCode() {
	
		return (int) (id % Integer.MAX_VALUE);
	}
}
